﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml;
using CSharpExt.Xml;

namespace CSharpExt.Util
{
    /// <summary>
    /// Legge un file di Properties avente la seguente forma:
    /// 
    ///     <Properties>
    ///         <Property name="..." value="..."/>
    ///     </Properties>
    /// 
    /// Eventualmente e' supportata anche la seguente sitnassi:
    /// 
    ///     <Properties>
    ///         <Properties name="node1">
    ///             ...
    ///             <Properties name="node1">
    ///                 <Property name="..." value="..."/>
    ///                 ...
    ///             </Properties>
    ///             ...
    ///         </Properties>
    ///         ...
    ///     </Properties>
    /// 
    /// In questo caso il nodo interno puo' essere indicato con le seguenti sintassi
    /// 
    ///     node1/node2
    ///     node1.node2
    /// 
    /// 
    /// </summary>
    public class Properties : Dictionary<String, String>
    {
        #region Static Operations

        public static Properties Load(String filePath)
        {
            Properties props = new Properties();

            /*
             *      <Properties>
             *          <Property name="..." value="..."/>
             *      </Properties>
             */
            XmlDocument xprops = DocumentParser.Parse(filePath);
            foreach (XmlElement xprop in xprops.DocumentElement.SelectNodes("./Property"))
            {
                String name  = xprop.GetAttribute("name");
                String value = xprop.GetAttribute("value");

                props[name] = value;
            }

            return props;
        }
        
        public static Properties Load(String filePath, String propertiesPath)
        {
            if (propertiesPath == null || "/".Equals(propertiesPath) || ".".Equals(propertiesPath))
                propertiesPath = String.Empty;
            
            XmlDocument xprops = DocumentParser.Parse(filePath);
            XmlElement propsNode = xprops.DocumentElement;
            
            String[] names = propertiesPath.Split('.', '/');
            foreach(String name in names)
            {
                if (propsNode == null)
                    break;
                
                String select = String.Format("Properties[@name='{0}']", name);
                propsNode = propsNode.SelectSingleNode(select) as XmlElement;
            }
            
            Properties props = new Properties();
            
            if (propsNode != null)
            foreach (XmlElement xprop in propsNode.SelectNodes("./Property"))
            {
                String name  = xprop.GetAttribute("name");
                String value = xprop.GetAttribute("value");

                props[name] = value;
            }

            return props;
        }

        #endregion

        #region Constructor

        public Properties()
        {

        }

        public Properties(Properties props)
        {
            this.Union(props);
        }

        #endregion

        #region Properties

        public String[] Names
        {
            get { return this.Keys.ToArray(); }
        }

        #endregion

        #region Read

        public bool HasProperty(String name)
        {
            return this.ContainsKey(name);
        }

        public String GetProperty(String name)
        {
            return Resolve(name, this[name]);
        }

        public bool GetProperty(String name, bool defaultValue)
        {
            String value;
            if (this.TryGetValue(name, out value))
            {
                return Boolean.Parse(value);
            }
            else
            {
                return defaultValue;
            }
        }

        public int GetProperty(String name, int defaultValue)
        {
            String value;
            if (this.TryGetValue(name, out value))
            {
                return int.Parse(value);
            }
            else
            {
                return defaultValue;
            }
        }

        public double GetProperty(String name, double defaultValue)
        {
            String value;
            if (this.TryGetValue(name, out value))
            {
                return double.Parse(value, CultureInfo.InvariantCulture);
            }
            else
            {
                return defaultValue;
            }
        }

        public String GetProperty(String name, String defaultValue)
        {
            String value;
            if (this.TryGetValue(name, out value))
            {
                return value;
            }
            else
            {
                return defaultValue;
            }
        }

        private String Resolve(String kname, String value)
        {
            int pos, end;
            String name;

            for(pos = value.IndexOf("${"); 
                pos != -1;
                pos = value.IndexOf("${"))
            {
                end = value.IndexOf("}", pos + 2);
                if (end == -1)
                    throw new SyntaxErrorException(String.Format("Syntax error on key {0}: '{1}'", kname, value));

                name = value.Substring(pos + 2, end - pos - 2);
                value = value.Substring(0, pos)
                        + GetProperty(name)
                        + value.Substring(end + 1);
            }

            return value;
        }

        #endregion

        #region Write

        public void SetProperty(String name, bool value)
        {
            this[name] = value.ToString();
        }

        public void SetProperty(String name, int value)
        {
            this[name] = value.ToString();
        }

        public void SetProperty(String name, double value)
        {
            this[name] = value.ToString();
        }

        public void SetProperty(String name, String value)
        {
            this[name] = value;
        }

        #endregion
    }

    internal class SyntaxErrorException : Exception
    {
        public SyntaxErrorException(string format)
        {
            throw new NotImplementedException();
        }
    }
}
